﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Reflection;

namespace SeguradoraIA.Framework.EnumHelper
{
    public class DescribedEnumHandler<T>
    {
        private readonly IDictionary<T, string> toDescription = new Dictionary<T, string>();
        private readonly IDictionary<string, T> fromDescription = new Dictionary<string, T>();

        private readonly IDictionary<T, string> toCode = new Dictionary<T, string>();
        private readonly IDictionary<string, T> fromCode = new Dictionary<string, T>();

        private const BindingFlags PUBLIC_STATIC = BindingFlags.Public | BindingFlags.Static;

        private void InitializeDescriptionAttributeDictionaries()
        {
            var type = typeof(T);
            var enumEntrys = from f in type.GetFields(PUBLIC_STATIC)
                             let attributes = f.GetCustomAttributes(typeof(DescriptionAttribute), false)
                             let description =
                                    attributes.Length == 1
                                            ? ((DescriptionAttribute)attributes[0]).Description
                                            : f.Name
                             select new
                             {
                                 Value = (T)Enum.Parse(type, f.Name),
                                 Description = description
                             };

            foreach (var enumEntry in enumEntrys)
            {
                toDescription[enumEntry.Value] = enumEntry.Description;
                fromDescription[enumEntry.Description] = enumEntry.Value;
            }
        }

        private void InitializaCodeAttributeDictionaries()
        {
            var type = typeof(T);
            var enumEntrys = from f in type.GetFields(PUBLIC_STATIC)
                             let attributes = f.GetCustomAttributes(typeof(CodeAttribute), false)
                             let code =
                                    attributes.Length == 1
                                            ? ((CodeAttribute)attributes[0]).Code
                                            : f.Name
                             select new
                             {
                                 Value = (T)Enum.Parse(type, f.Name),
                                 Code = code
                             };

            foreach (var enumEntry in enumEntrys)
            {
                toCode[enumEntry.Value] = enumEntry.Code;
                fromCode[enumEntry.Code] = enumEntry.Value;
            }
        }

        public DescribedEnumHandler()
        {
            InitializeDescriptionAttributeDictionaries();
            InitializaCodeAttributeDictionaries();
        }

        public IDictionary<T, string> GetDescriptions()
        {
            return this.toDescription;
        }

        /// <summary>
        /// Extracts the description for the given enum value.
        /// <remarks>if no description was set for the given value, it's name will be retrieved</remarks>
        /// </summary>
        /// <param name="value">The enum value</param>
        /// <returns>The value's description</returns>
        public string GetDescriptionFrom(T value)
        {
            return toDescription[value];
        }

        /// <summary>
        /// Parse the given string and return the enum value for with the given string acts as description
        /// </summary>
        /// <param name="description">The given description</param>
        /// <returns>A matching enum value</returns>
        public T GetValueFromDescription(string description)
        {
            return fromDescription[description];
        }

        /// <summary>
        /// Extracts the code for the given enum value.
        /// <remarks>if no code was set for the given value, it's name will be retrieved</remarks>
        /// </summary>
        /// <param name="value">The enum value</param>
        /// <returns>The value's code</returns>
        public string GetCodeFrom(T value)
        {
            return toCode[value];
        }

        /// <summary>
        /// Parse the given string and return the enum value for with the given string acts as code
        /// </summary>
        /// <param name="code">The given code</param>
        /// <returns>A matching enum value</returns>
        public T GetValueFromCode(string code)
        {
            return fromCode[code];
        }
    }
}
